<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
<html lang="zh-CN"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta http-equiv="Content-Language" content="zh-CN"><link href="stylesheet.css" media="all" rel="stylesheet" type="text/css">
<title>书写一个过程语言处理器</title>
<script> var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?d286c55b63a3c54a1e43d10d4c203e75"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); </script>
</head><body class="CHAPTER">
<div>
<table summary="Header navigation table" width="100%" border="0" cellpadding="0" cellspacing="0">
<tr><th colspan="5" align="center" valign="bottom">PostgreSQL 8.2.3 中文文档</th></tr>
<tr><td width="10%" align="left" valign="top"><a href="nls-programmer.html" accesskey="P">后退</a></td><td width="10%" align="left" valign="top"><a href="nls.html">快退</a></td><td width="60%" align="center" valign="bottom"></td><td width="10%" align="right" valign="top"><a href="geqo.html">快进</a></td><td width="10%" align="right" valign="top"><a href="geqo.html" accesskey="N">前进</a></td></tr>
</table>
<hr align="LEFT" width="100%"></div>
<div class="CHAPTER"><h1><a name="PLHANDLER"></a>章47. 书写一个过程语言处理器</h1><a name="AEN68571"></a>
<p>调用函数的时候，如果函数的书写语言不是目前的"版本-1"的编译语言接口(这包括用户定义的过程语言写的函数，用 SQL 写的函数，以及用版本 0 的编译语言接口写的函数)，都会通过一个<i class="FIRSTTERM">调用处理器</i>处理具体的语言。调用处理器有责任用一种有意义的方法执行这个函数，比如说解析所提供的文本等等。本章简介如何书写一个新的过程语言调用处理器。</p>
<p>过程语言的调用处理器是一个"普通"的函数，必须使用一种编译语言来写，比如 C ，使用"版本-1"的接口，并且在 PostgreSQL 里注册成接受零个参数并且返回类型 <tt class="TYPE">language_handler</tt> 。这个特殊的伪类型标识该函数为一个调用处理器并且避免它直接在 SQL 命令中被调用。</p>
<p>调用处理器的调用方式和其它函数一样：它接受一个指向一个 <tt class="STRUCTNAME">FunctionCallInfoData</tt> <tt class="TYPE">struct</tt> 的指针，这个指针包含参数值和有关被调用的函数的信息，并且预期它返回一个 <tt class="TYPE">Datum</tt> 结果(如果它希望返回一个 SQL 的空结果，那么可能设置 <tt class="STRUCTFIELD">isnull</tt> 字段)。调用处理器和普通的被调函数的区别是 <tt class="STRUCTNAME">FunctionCallInfoData</tt> 结构的 <tt class="STRUCTFIELD">flinfo-&gt;fn_oid</tt> 字段强包含实际要调用的函数的 OID ，而不是调用处理器自身。调用处理器必须使用这个字段判断要执行哪个函数。通常，传递进来的参数列表也是按照目标函数的声明设置的，而不是给调用处理器设置的。</p>
<p>从系统表 <code class="CLASSNAME">pg_proc</code> 里抓取函数入口以及分析被调函数的参数和返回类型就是调用处理器的事了。来自 <tt class="COMMAND">CREATE FUNCTION</tt> 命令中的 <tt class="LITERAL">AS</tt> 子句将会在 <code class="CLASSNAME">pg_proc</code> 行的 <tt class="LITERAL">prosrc</tt> 字段中找到。这个通常是过程语言本身的源文本，但也可以是别的东西，比如一个指向某个文件的路径名，或者任何告诉调用处理器如何详细处理的东西。</p>
<p>通常，每个 SQL 语句里面可能要调用同一个函数多次。调用处理器可以利用 <tt class="STRUCTFIELD">flinfo-&gt;fn_extra</tt> 字段避免重复地查找有关被调函数地信息。这个字段初始为 <tt class="SYMBOL">NULL</tt> ，但是可以被调用处理器设置为指向有关被调函数的信息。在随后的调用中，如果 <tt class="STRUCTFIELD">flinfo-&gt;fn_extra</tt> 已经为非 <tt class="SYMBOL">NULL</tt> ，那么就可以直接使用它而免于重新查找信息。调用处理器必须确保 <tt class="STRUCTFIELD">flinfo-&gt;fn_extra</tt> 是用于指向一块至少会生存到当前查询结束的内存区里，因为一个 <tt class="STRUCTNAME">FmgrInfo</tt> 数据结构将会保存那么长的时间。一个实现这个要求的方法是在 <tt class="STRUCTFIELD">flinfo-&gt;fn_mcxt</tt> 声明的内存环境里分配一块额外的数据；这样的数据通常和 <tt class="STRUCTNAME">FmgrInfo</tt> 自己有一样的生命期。但是处理器也可以同样选择使用一个更长生存期的环境，这样它就可以跨查询缓存函数定义。</p>
<p>在过程语言函数以触发器的形式调用的时候，就没有什么参数以通常的方式传递进来，而是 <tt class="STRUCTNAME">FunctionCallInfoData</tt> 的 <tt class="STRUCTFIELD">context</tt> 字段指向一个 <tt class="STRUCTNAME">TriggerData</tt> 结构，而不是像普通函数调用里面的 <tt class="SYMBOL">NULL</tt> 那样。一个语言处理器应该为过程语言函数提供获取触发器信息的机制。</p>
<p>下面是一个用 C 写的存储过程语言处理器的模版：</p>
<pre class="PROGRAMLISTING">#include "postgres.h"
#include "executor/spi.h"
#include "commands/trigger.h"
#include "fmgr.h"
#include "access/heapam.h"
#include "utils/syscache.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"

PG_FUNCTION_INFO_V1(plsample_call_handler);

Datum
plsample_call_handler(PG_FUNCTION_ARGS)
{
    Datum          retval;

    if (CALLED_AS_TRIGGER(fcinfo))
    {
        /*
         * 以触发器过程调用
         */
        TriggerData    *trigdata = (TriggerData *) fcinfo-&gt;context;

        retval = ...
    }
    else
    {
        /*
         * 以函数调用
         */

        retval = ...
    }

    return retval;
}</pre>
<p>在打点的地方放上几千行代码就可以完成调用处理器。</p>
<p>在把处理器函数编译成一个可加载的模块(参阅<a href="xfunc-c.html#DFUNC">节33.9.6</a>)之后，下面的命令就可以注册这个例子过程语言：</p>
<pre class="PROGRAMLISTING">CREATE FUNCTION plsample_call_handler() RETURNS language_handler
    AS '<tt class="REPLACEABLE"><i>filename</i></tt>'
    LANGUAGE C;
CREATE LANGUAGE plsample
    HANDLER plsample_call_handler;</pre>
<p>如果想书写自己的调用处理器，那么包含在标准发布里面的过程语言是很好的例子。参考一下源代码树中的 <tt class="FILENAME">src/pl</tt> 子目录。</p>
</div>
<div>
<hr align="LEFT" width="100%">
<table summary="Footer navigation table" width="100%" border="0" cellpadding="0" cellspacing="0">
<tr><td width="33%" align="left" valign="top"><a href="nls-programmer.html" accesskey="P">后退</a></td><td width="34%" align="center" valign="top"><a href="index.html" accesskey="H">首页</a></td><td width="33%" align="right" valign="top"><a href="geqo.html" accesskey="N">前进</a></td></tr>
<tr><td width="33%" align="left" valign="top">寄语程序员</td><td width="34%" align="center" valign="top"><a href="internals.html" accesskey="U">上一级</a></td><td width="33%" align="right" valign="top">基因查询优化器</td></tr>
</table>
</div>
</body></html>